package simpledb.buffer;

import simpledb.file.*;
import simpledb.server.SimpleDB;


/**
 * An individual buffer.
 * A buffer wraps a page and stores information about its status,
 * such as the disk block associated with the page,
 * the number of times the block has been pinned,
 * whether the contents of the page have been modified,
 * and if so, the id of the modifying transaction and
 * the LSN of the corresponding log record. 
 * @author Edward Sciore
 */

/**
 * 
 * @author Conan
 *
 */
public class Buffer {
	private Page contents = new Page();
	private int blk = -1;
	private int pins = 0;
	private int modifiedBy = -1;
	private int logSequenceNumber;
	private Buffer nextAvailable = null;
	private Buffer preAvailable = null;

	Buffer getNextAvailable(){ return nextAvailable; }
	Buffer getPreAvailable(){ return preAvailable; }
	void setNextAvailable(Buffer next){ nextAvailable = next; }
	void setPreAvailable(Buffer pre){ preAvailable = pre; }
	
	/**
	 * Returns the integer value at the specified offset of the
	 * buffer's page. 
	 * If an integer was not stored at that location,
	 * the behavior of the method is unpredictable.
	 * @param offset the byte offset of the page
	 * @return the integer value at that offset
	 */
	public int getInt(int offset) {
		return contents.getInt(offset);
	}

	/**
	 * Returns the string value at the specified offset of the
	 * buffer's page. 
	 * If a string was not stored at that location,
	 * the behavior of the method is unpredictable.
	 * @param offset the byte offset of the page
	 * @return the string value at that offset
	 */
	public String getString(int offset) {
		return contents.getString(offset);
	}

	/**
	 * Writes an integer to the specified offset of the
	 * buffer's page.
	 * This method assumes that the transaction has already
	 * written an appropriate log record.  
	 * The buffer saves the id of the transaction 
	 * and the LSN of the log record. 
	 * @param offset the byte offset within the page
	 * @param val the new integer value to be written
	 * @param txnum the id of the transaction performing the modification
	 * @param lsn the LSN of the corresponding log record
	 */
	public void setInt(int offset, int val, int txnum, int lsn) {
		modifiedBy = txnum;
		logSequenceNumber = lsn;
		contents.setInt(offset, val);
	}

	/**
	 * Writes a string to the specified offset of the
	 * buffer's page.
	 * This method assumes that the transaction has already
	 * written an appropriate log record.  
	 * The buffer saves the id of the transaction 
	 * and the LSN of the log record. 
	 * @param offset the byte offset within the page
	 * @param val the new string value to be written
	 * @param txnum the id of the transaction performing the modification
	 * @param lsn the LSN of the corresponding log record
	 */
	public void setString(int offset, String val, int txnum, int lsn) {
		modifiedBy = txnum;
		logSequenceNumber = lsn;
		contents.setString(offset, val);
	}

	/**
	 * Returns a reference to the disk block
	 * that the buffer is pinned to.
	 * @return a reference to a disk block
	 */
	public int block() {
		return blk;
	}

	/**
	 * Writes the page to its disk block if the
	 * page is dirty. 
	 * The method ensures that the corresponding log
	 * record has been written to disk prior to writing
	 * the page to disk.
	 */
	void flush() {
		if (modifiedBy >= 0) {
//			SimpleDB.logMgr().flush(logSequenceNumber);
			contents.write(blk);
		}
		modifiedBy = -1;
	}

	/**
	 * Increases the buffer's pin count. 
	 */
	void pin() {
		pins++;
	}

	/**
	 * Decreases the buffer's pin count.
	 */
	void unpin() {
		pins--;
	}

	/**
	 * Returns true if the buffer is currently pinned
	 * (that is, if it has a nonzero pin count).
	 * @return true if the buffer is pinned
	 */
	boolean isPinned() {
		return pins > 0;
	}

	/**
	 * Returns true if the buffer is dirty
	 * due to a modification by the specified transaction.
	 * @param txnum the id of the transaction
	 * @return true if the transaction modified the buffer
	 */
	boolean isModifiedBy(int txnum) {
		return txnum == modifiedBy;
	}

	/**
	 * Reads the contents of the specified block into 
	 * the buffer's page.
	 * If the buffer was dirty, then the contents
	 * of the previous page are first written to disk. 
	 * @param b a reference to the data block
	 */
	void assignToBlock(int b) {
		flush();
		blk = b;
		contents.read(blk);
		pins = 0;
	}

	/**
	 * Initializes the buffer's page according to the specified formatter,
	 * and appends the page to the specified file.
	 * If the buffer was dirty, then the contents
	 * of the previous page are first written to disk. 
	 * @param filename the name of the file
	 * @param fmtr a pageformatter, used to initialize the page
	 */
	void alloc(PageFormatter fmtr) {
		flush();
		fmtr.format(contents);
		blk = contents.append();
		pins = 0;
	}
	
	int free(){
		int res = contents.free(blk);
		blk = -1;
		pins = 0;
		modifiedBy = -1;
		return res;
	}
}